<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>Oatpp教程</title>
        <meta name="robots" content="noindex" />
        <!-- Custom HTML head -->
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="favicon.svg">
        <link rel="shortcut icon" href="favicon.png">
        <link rel="stylesheet" href="css/variables.css">
        <link rel="stylesheet" href="css/general.css">
        <link rel="stylesheet" href="css/chrome.css">
        <link rel="stylesheet" href="css/print.css" media="print">
        <!-- Fonts -->
        <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="fonts/fonts.css">
        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="highlight.css">
        <link rel="stylesheet" href="tomorrow-night.css">
        <link rel="stylesheet" href="ayu-highlight.css">

        <!-- Custom theme stylesheets -->
    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script>
            var path_to_root = "";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script>
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script>
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script>
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="DTO.html"><strong aria-hidden="true">1.</strong> DTO</a></li><li class="chapter-item expanded "><a href="Async概述.html"><strong aria-hidden="true">2.</strong> Async</a></li><li class="chapter-item expanded "><a href="Simple_vs_Async.html"><strong aria-hidden="true">3.</strong> Simple vs Async</a></li><li class="chapter-item expanded "><a href="协程.html"><strong aria-hidden="true">4.</strong> 协程</a></li><li class="chapter-item expanded "><a href="关系型对象映射框架.html"><strong aria-hidden="true">5.</strong> ORM</a></li><li class="chapter-item expanded "><a href="Monolithization.html"><strong aria-hidden="true">6.</strong> Monolithization</a></li><li class="chapter-item expanded "><a href="步骤指南.html"><strong aria-hidden="true">7.</strong> 步骤指南</a></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">Oatpp教程</h1>

                    <div class="right-buttons">
                        <a href="print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>
                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>
                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script>
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="dto"><a class="header" href="#dto">DTO</a></h1>
<p>DTO是继承于<code>oatpp::DTO</code> 的对象，用于序列化和反序列化<code>oatpp::data::mapping::ObjectMapper</code> 的一个特殊对象</p>
<h2 id="介绍"><a class="header" href="#介绍">介绍</a></h2>
<p>DTO对象采用DTO代码创建块创建，必须从<code>#include OATPP_CODEGEN_BEGIN(DTO)</code> 开始，以<code>#include OATPP_CODEGEN_END(DTO)</code> 结束，<em>不要忘记关闭代码创建块，防止接下来的代码产生宏冲突</em></p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/core/Types.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;

#include OATPP_CODEGEN_BEGIN(DTO) ///&lt; Begin DTO codegen section

class User : public oatpp::DTO {

  DTO_INIT(User, DTO /* extends */)

  DTO_FIELD(String, name);
  DTO_FIELD(Int32, age);

};

#include OATPP_CODEGEN_END(DTO) ///&lt; End DTO codegen section
</code></pre>
<h2 id="字段名修饰"><a class="header" href="#字段名修饰">字段名修饰</a></h2>
<pre><code class="language-text-x-c-src">DTO_FIELD(String, name, &quot;user-name&quot;);
</code></pre>
<h3 id="默认值"><a class="header" href="#默认值">默认值</a></h3>
<p>默认所有值都设置为nullptr,你可以分配一个值来覆盖DTO字段的默认值</p>
<pre><code class="language-text-x-c-src">DTO_FIELD(String, name) = &quot;Ivan&quot;;
</code></pre>
<h3 id="添加字段信息"><a class="header" href="#添加字段信息">添加字段信息</a></h3>
<pre><code class="language-text-x-c-src">DTO_FIELD_INFO(name) {
  info-&gt;description = &quot;user first name&quot;; //在Swagger-UI中加入该字段的描述
}
DTO_FIELD(String, name) = &quot;Ivan&quot;
</code></pre>
<h3 id="声明字段是一个对象"><a class="header" href="#声明字段是一个对象">声明字段是一个对象</a></h3>
<pre><code class="language-text-x-c-src">DTO_FIELD(Object&lt;User&gt;, user);
</code></pre>
<h3 id="声明字段是一个列表"><a class="header" href="#声明字段是一个列表">声明字段是一个列表</a></h3>
<p>基本类型列表</p>
<pre><code class="language-text-x-c-src">DTO_FIELD(List&lt;Int32&gt;, colors);
</code></pre>
<p>对象列表</p>
<pre><code class="language-text-x-c-src">DTO_FIELD(List&lt;Object&lt;MyObject&gt;&gt;, colors);
</code></pre>
<h3 id="声明字段是一个map"><a class="header" href="#声明字段是一个map">声明字段是一个Map</a></h3>
<p>Map <code>String – &gt; Int32</code></p>
<pre><code class="language-text-x-c-src">DTO_FIELD(Fields&lt;Int32&gt;, colors);
</code></pre>
<p>Map <code>String – &gt; Object</code></p>
<pre><code class="language-text-x-c-src">DTO_FIELD(Fields&lt;Object&lt;MyObject&gt;&gt;, colors);
</code></pre>
<h2 id="类型"><a class="header" href="#类型">类型</a></h2>
<h3 id="核心类型"><a class="header" href="#核心类型">核心类型</a></h3>
<p>这些类型定义在<code>oatpp/core/Types.cpp</code></p>
<p><strong>基础类型</strong></p>
<div class="table-wrapper"><table><thead><tr><th></th><th></th><th></th></tr></thead><tbody>
<tr><td>Type</td><td>Underlying</td><td>Default Value</td></tr>
<tr><td>Int8</td><td>v_int8</td><td>nullptr</td></tr>
<tr><td>UInt8</td><td>v_uint8</td><td>nullptr</td></tr>
<tr><td>Int16</td><td>v_int16</td><td>nullptr</td></tr>
<tr><td>UInt16</td><td>v_uint16</td><td>nullptr</td></tr>
<tr><td>Int32</td><td>v_int32</td><td>nullptr</td></tr>
<tr><td>UInt32</td><td>v_uint32</td><td>nullptr</td></tr>
<tr><td>Int64</td><td>v_int64</td><td>nullptr</td></tr>
<tr><td>UInt64</td><td>v_uint64</td><td>nullptr</td></tr>
<tr><td>Float32</td><td>v_float32</td><td>nullptr</td></tr>
<tr><td>Float64</td><td>v_float64</td><td>nullptr</td></tr>
<tr><td>Boolean</td><td>bool</td><td>nullptr</td></tr>
</tbody></table>
</div>
<p>示例</p>
<pre><code class="language-text-x-c-src">oatpp::Int32 a = 32;
v_int32 va = *a;
</code></pre>
<p><strong>集合</strong></p>
<div class="table-wrapper"><table><thead><tr><th></th><th></th><th></th></tr></thead><tbody>
<tr><td>Type</td><td>Underlying Collection</td><td>Default Value</td></tr>
<tr><td>Vector<T></td><td>std::vector<T></td><td>nullptr</td></tr>
<tr><td>List<T></td><td>std::list<T></td><td>nullptr</td></tr>
<tr><td>UnorderedSet</td><td>std::unordered_set<T></td><td>nullptr</td></tr>
<tr><td>Fields<T></td><td>std::list&lt;std::pair&lt;Key,Value&gt;&gt;</td><td>nullptr</td></tr>
<tr><td>UnorderedFields<T></td><td>std::unordered_map&lt;key,value&gt;</td><td>nullptr</td></tr>
</tbody></table>
</div>
<p>示例</p>
<pre><code class="language-text-x-c-src">oatpp::Vector&lt;oatpp::String&gt; porridges = {&quot;Owsianka&quot;, &quot;Stirabout&quot;, &quot;Zabkása&quot;};
for(auto&amp; porridge : *porridges) {
  ...
}
</code></pre>
<p><strong>特殊类型</strong></p>
<div class="table-wrapper"><table><thead><tr><th></th><th></th><th></th></tr></thead><tbody>
<tr><td>Type</td><td>Underlying Type</td><td>Default Value</td></tr>
<tr><td>String</td><td>oatpp::base::StrBuffer</td><td>nullptr</td></tr>
<tr><td>Object<T></td><td>从oatpp::DTO中继承而来</td><td>nullptr</td></tr>
<tr><td>Enum<T></td><td>通过enum声明</td><td>nullptr</td></tr>
<tr><td>Any</td><td>任何可使用映射的类型</td><td>nullptr</td></tr>
</tbody></table>
</div>
<p>示例</p>
<pre><code class="language-text-x-c-src">oatpp::Object&lt;MyDto&gt; dto = MyDto::createShared();
oatpp::Any any = dto;
...
auto dto = any.retrieve&lt;oatpp::Object&lt;MyDto&gt;&gt;(); //如果存储的类型不匹配会抛出std::runtime_error异常
</code></pre>
<h3 id="自定义类型"><a class="header" href="#自定义类型">自定义类型</a></h3>
<p>为了简化将自定义类型结合到oatpp的ObjectMapping框架中，这里引入<strong>Type Interpretation</strong>特性</p>
<p>现在你有一些结构体，但是还未结合到oatpp的对象框架中</p>
<pre><code class="language-text-x-c-src">struct VPoint {
  v_int32 x;
  v_int32 y;
  v_int32 z;
};
</code></pre>
<p>要将其合并你可以根据下面的代码</p>
<pre><code class="language-text-x-c-src">namespace __class {
  class PointClass;
}

//在你的类型中声明ObjectWrapper，这里类型别名为Point
typedef oatpp::data::mapping::type::Primitive&lt;VPoint, __class::PointClass&gt; Point;

namespace __class {

  //类型信息
  class PointClass {
  private:

	//类型声明
    class Inter : public oatpp::Type::Interpretation&lt;Point, oatpp::UnorderedFields&lt;oatpp::Int32&gt;&gt;  {
    public:

      oatpp::UnorderedFields&lt;oatpp::Int32&gt; interpret(const Point&amp; value) const override {
          return {{&quot;x&quot;, value-&gt;x}, {&quot;y&quot;, value-&gt;y}, {&quot;z&quot;, value-&gt;z}};
      }

      Point reproduce(const oatpp::UnorderedFields&lt;oatpp::Int32&gt; map) const override {
        return Point({map[&quot;x&quot;], map[&quot;y&quot;], map[&quot;z&quot;]});
      }

    };

  public:

    static const oatpp::ClassId CLASS_ID;

    static oatpp::Type* getType(){
      static Type type(CLASS_ID, nullptr, nullptr, {{&quot;my-types&quot;, new Inter()}} /*添加类型声明*/ );
      return &amp;type;
    }

  };

  const oatpp::ClassId PointClass::CLASS_ID(&quot;my-types::Point&quot;);

}
</code></pre>
<p>现在Point对象可以在对象映射中序列化/反序列化</p>
<pre><code class="language-text-x-c-src">oatpp::parser::json::mapping::ObjectMapper mapper;

{
  auto config = mapper.getSerializer()-&gt;getConfig();
  config-&gt;enabledInterpretations = {&quot;my-types&quot;}; //使能序列化
}

{
  auto config = mapper.getDeserializer()-&gt;getConfig();
  config-&gt;enabledInterpretations = {&quot;my-types&quot;}; //使能反序列化
}

Point point ({1, 2, 3}); //创建对象Point

auto json = mapper.writeToString(point); //序列化Point
auto pointClone = mapper.readFromString&lt;Point&gt;(json); //反序列化Point
</code></pre>
<blockquote>
<p>类型可以融合进所有的框架组件中，包括REST 框架，ORM，Swagger-UI</p>
</blockquote>
<h2 id="例子"><a class="header" href="#例子">例子</a></h2>
<h3 id="序列化反序列化"><a class="header" href="#序列化反序列化">序列化/反序列化</a></h3>
<p><strong>定义DTO</strong></p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/core/Types.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;

#include OATPP_CODEGEN_BEGIN(DTO) //开始DTO代码创建段

class User : public oatpp::DTO {

  DTO_INIT(User, DTO /*基类*/)

  DTO_FIELD(String, name, &quot;First-Name&quot;);
  DTO_FIELD(String, surname, &quot;Family-Name&quot;);
  DTO_FIELD(Int32, age);
  DTO_FIELD(Fields&lt;List&lt;Object&lt;User&gt;&gt;&gt;, familyMembers); ///&lt; Map&lt;String, List&lt;User&gt;&gt;
  DTO_FIELD(Fields&lt;String&gt;, additionalNotes); ///&lt; Map&lt;String, String&gt;

};

#include OATPP_CODEGEN_END(DTO) //结束DTO代码创建段
</code></pre>
<p><strong>创建对象和设置字段</strong></p>
<pre><code class="language-text-x-c-src">//创建User对象
auto user = User::createShared();
user-&gt;name = &quot;Ivan&quot;;
user-&gt;surname = &quot;Ovsyanochka&quot;;
user-&gt;age = 24;
user-&gt;familyMembers = {}; //初始化空的Map容器
user-&gt;additionalNotes = {}; //初始化空的Map容器

auto brother = User::createShared();
brother-&gt;name = &quot;Yuriy&quot;;
brother-&gt;surname = &quot;Ovsyanochka&quot;;
brother-&gt;age = 30;

auto sister = User::createShared();
sister-&gt;name = &quot;Kate&quot;;
sister-&gt;surname = &quot;Ovsyanochka&quot;;
sister-&gt;age = 20;

//创建siblings列表
oatpp::List&lt;oatpp::Object&lt;User&gt;&gt; siblings = {brother, sister};

user-&gt;familyMembers-&gt;push_back({&quot;siblings&quot;, siblings});
user-&gt;additionalNotes-&gt;push_back({&quot;Education&quot;, &quot;Master of Computer Science&quot;});
</code></pre>
<p><strong>创建JSON对象映射</strong></p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;

...

//采用默认配置创建json的ObjectMapper
auto jsonObjectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared();
</code></pre>
<p><strong>序列化user对象到JSON</strong></p>
<pre><code class="language-text-x-c-src">oatpp::String json = jsonObjectMapper-&gt;writeToString(user); 
OATPP_LOGD(&quot;json&quot;, &quot;value='%s'&quot;, json-&gt;c_str()); //打印JSON
</code></pre>
<p>输出:</p>
<pre><code class="language-application-json">{
  &quot;First-Name&quot;: &quot;Ivan&quot;,
  &quot;Family-Name&quot;: &quot;Ovsyanochka&quot;,
  &quot;age&quot;: 24,
  &quot;familyMembers&quot;: {
    &quot;siblings&quot;: [
      {
        &quot;First-Name&quot;: &quot;Yuriy&quot;,
        &quot;Family-Name&quot;: &quot;Ovsyanochka&quot;,
        &quot;age&quot;: 30,
        &quot;familyMembers&quot;: null,
        &quot;additionalNotes&quot;: null
      },
      {
        &quot;First-Name&quot;: &quot;Kate&quot;,
        &quot;Family-Name&quot;: &quot;Ovsyanochka&quot;,
        &quot;age&quot;: 20,
        &quot;familyMembers&quot;: null,
        &quot;additionalNotes&quot;: null
      }
    ]
  },
  &quot;additionalNotes&quot;: {
    &quot;Education&quot;: &quot;Master of Computer Science&quot;
  }
}
</code></pre>
<blockquote>
<p>如果需要更好的输出可以使用<code>oatpp::parser::json::Beautifier</code></p>
</blockquote>
<p>反序列化字符串</p>
<pre><code class="language-text-x-c-src">auto cloneOfUser = jsonObjectMapper-&gt;readFromString&lt;oatpp::Object&lt;User&gt;&gt;(json);
</code></pre>
<h3 id="使用json美化器"><a class="header" href="#使用json美化器">使用JSON美化器</a></h3>
<p>不使用美化器时，输出是没有空格和换行符的</p>
<pre><code class="language-text-x-c-src">{&quot;First-Name&quot;:&quot;Ivan&quot;,&quot;Family-Name&quot;:&quot;Ovsyanochka&quot;,&quot;age&quot;:24 ...
</code></pre>
<p>为了美化json，需要在设置中设置<code>useBeautifier = true</code> 在序列化配置中：</p>
<pre><code class="language-text-x-c-src">//创建序列化和反序列化配置
auto serializeConfig = oatpp::parser::json::mapping::Serializer::Config::createShared();
auto deserializeConfig = oatpp::parser::json::mapping::Deserializer::Config::createShared();

//启动美化器
serializeConfig-&gt;useBeautifier = true;

//采用序列化配置创建JSON对象映射
auto jsonObjectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared(serializeConfig, deserializeConfig);
</code></pre>
<p>更多关于json对象映射的配置可以看这些:</p>
<p><a href="https://oatpp.io/api/latest/oatpp/parser/json/mapping/Serializer/#serializer-config">Serializer::Config</a></p>
<p><a href="https://oatpp.io/api/latest/oatpp/parser/json/mapping/Deserializer/#deserializer-config">Deserializer::Config</a></p>
<h2 id="代码示例"><a class="header" href="#代码示例">代码示例</a></h2>
<p> <a href="https://github.com/oatpp/oatpp-consul/blob/master/src/oatpp-consul/rest/DTOs.hpp">https://github.com/oatpp/oatpp-consul/blob/master/src/oatpp-consul/rest/DTOs.hpp</a></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="async概述"><a class="header" href="#async概述">Async概述</a></h1>
<p>Async方法可以粗略的描述为通过非堵塞操作在循环中迭代，Async操作是不会被堵塞的。如果被堵塞了，这通常是陷入了循环和Async系统正在等待操作结束，这将会带来代码编写上的挑战。当然，这可以更好的利用CPU资源，并且提高一些性能。</p>
<p>如果你打算使用Async方法，在任何时候都应该确保：</p>
<ul>
<li>使用的是非堵塞IO方法</li>
<li>你自己所编写的异步方法也是非堵塞的</li>
</ul>
<p>在Oatpp中，所有异步逻辑都是基于<code>oatpp::async::Executor</code> 和<code>oatpp::coroutines</code></p>
<p>提供Async的实例被命名为<code>AsyncEntity</code> ，Async方法被命名为<code>doSomethingAsync</code> ，生成Async的宏被命名为<code>MACRO_ASYNC</code></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="api---simple-vs-async"><a class="header" href="#api---simple-vs-async">API - Simple vs Async</a></h1>
<p><strong>Simple</strong> : 多线程+堵塞IO</p>
<p><strong>Async</strong> : 协程+非堵塞IO</p>
<h3 id="线程使用量"><a class="header" href="#线程使用量">线程使用量</a></h3>
<p><strong>Simple</strong> : 连接数 + 一根接收线程</p>
<p><strong>Async</strong> : 异步处理器 + 一根接收线程</p>
<h3 id="api区别"><a class="header" href="#api区别">API区别</a></h3>
<p><strong>Simple</strong> :</p>
<ul>
<li><code>ConnectionHandler = HttpConnectionHandler</code></li>
<li>在<code>ApiController</code> 中采用<code>ENDPOINT</code></li>
</ul>
<p><strong>Async</strong> :</p>
<ul>
<li><code>ConnectionHandler = AsyncHttpConnectionHandler</code></li>
<li>在<code>ApiController</code> 中采用<code>ENDPOINT_ASYNC</code></li>
</ul>
<h2 id="代码示例-1"><a class="header" href="#代码示例-1">代码示例</a></h2>
<h3 id="simple"><a class="header" href="#simple">Simple</a></h3>
<p><strong>Config</strong></p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;
#include &quot;oatpp/web/server/HttpRouter.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;
#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;
#include &quot;oatpp/core/macro/component.hpp&quot;

class AppComponent {
public:

  //创建ConnectionProvider组建去监听端口
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, serverConnectionProvider)([] {
    return oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});
  }());

  //创建路由组件
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, httpRouter)([] {
    return oatpp::web::server::HttpRouter::createShared();
  }());
  
  //创建路由组建用于路由请求
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, serverConnectionHandler)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router); //获取路由组件
    return oatpp::web::server::HttpConnectionHandler::createShared(router);
  }());

};
</code></pre>
<p><strong>Controller</strong></p>
<pre><code class="language-text-x-c-src">ENDPOINT(&quot;POST&quot;, &quot;demo/api/json&quot;, postJson,
         BODY_DTO(Object&lt;MyDto&gt;, dto)) {
  auto dtoMessage = dto-&gt;message;
  return createResponse(Status::CODE_200, &quot;dtoMessage: &quot; + dtoMessage);
}
</code></pre>
<h3 id="async"><a class="header" href="#async">Async</a></h3>
<p><strong>config</strong></p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/web/server/AsyncHttpConnectionHandler.hpp&quot;
#include &quot;oatpp/web/server/HttpRouter.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;
#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;
#include &quot;oatpp/core/macro/component.hpp&quot;

class AppComponent {
public:

  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, serverConnectionProvider)([] {
    return oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});
  }());

  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, httpRouter)([] {
    return oatpp::web::server::HttpRouter::createShared();
  }());

  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, serverConnectionHandler)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router); // get Router component
    //这一步就用到了AsyncConnectionHandler
    return oatpp::web::server::AsyncHttpConnectionHandler::createShared(router);
  }());

};
</code></pre>
<p><strong>Controller</strong></p>
<pre><code class="language-text-x-c-src">ENDPOINT_ASYNC(&quot;POST&quot;, &quot;demo/api_async/json&quot;, PostJSONAsync) {

  ENDPOINT_ASYNC_INIT(PostJSONAsync)

  Action act() override {
    return request-&gt;readBodyToDtoAsync&lt;oatpp::Object&lt;MyDto&gt;&gt;(controller-&gt;getDefaultObjectMapper())
                   .callbackTo(&amp;PostJSONAsync::onBodyObtained);
  }

  Action onBodyObtained(const oatpp::Object&lt;MyDto&gt;&amp; dto) {
    return _return(controller-&gt;createResponse(Status::CODE_200, &quot;dtoMessage: &quot; + dto-&gt;message));
  }

};
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="协程"><a class="header" href="#协程">协程</a></h1>
<p>Oat++的协程非传统意义上的协程</p>
<p>Oat++采用自定义无状态的协程调度，这种调度提供了额外的优化空间和更好的利用CPU资源</p>
<p>Oat++是通过<code>oatpp::async::Executor</code> 来执行协程的。在一些迭代中，协程会返回<code>oatpp::async::Action</code> 去告诉执行者下一步要做什么。通过这种方式，Oat++ Async  processor重新调度协程去完成相关工作。</p>
<h2 id="async-executor"><a class="header" href="#async-executor">Async Executor</a></h2>
<p><code>oatpp::async::Executor</code> 分配三个工作组，通过设置数字来为每一个工作组指定线程数量</p>
<pre><code class="language-text-x-c-src">oatpp::async::Executor executor(
    1 /* data-processing workers */, 
    1 /* I/O workers */, 
    1 /* timer workers */
);
</code></pre>
<p>所有的协程初始化是通过 data-processing 工作组，可能会重新调度IO或定时器，这些都取决于<code>oatpp::async::Action</code> 返回的协程迭代器</p>
<p><img src="%E5%8D%8F%E7%A8%8B/image.png" alt="" /></p>
<blockquote>
<p>虽然Oat++ 异步处理器会重新调度协程到不同的线程中，但是协程的创建和销毁是在同一个线程中的</p>
</blockquote>
<h3 id="io-worker"><a class="header" href="#io-worker">I/O Worker</a></h3>
<p>对于I/O <code>oatpp::async::Executor</code> 采用基于事件IO的实现<code>IOEventWorker</code></p>
<ul>
<li>Mac/BSD系统采用基于kqueue的实现</li>
<li>Linux系统采用基于epoll的实现</li>
</ul>
<p>当协程返回的Action类型为TYPE_IO_WAIT，在kqueue/epoll中将重新分配I/O worker的文件描述符</p>
<p>因此OAT++不会浪费CPU的资源在快速轮询的长等待连接上。</p>
<h3 id="api"><a class="header" href="#api">API</a></h3>
<p>在Oatpp中，协程是从<code>oatpp::async::Coroutine</code>或<code>oatpp::async::CoroutineWithResult</code>中继承而来</p>
<p>协程在<code>oatpp::async::Executor</code> 中被处理</p>
<pre><code class="language-text-x-c-src">class MyCoroutine : public oatpp::async::Coroutine&lt;MyCoroutine&gt; {
public:

  //act() : 是协程返回Action的入口，用来告诉worker下一步要干嘛
  Action act() override {
    OATPP_LOGD(&quot;MyCoroutine&quot;, &quot;act()&quot;);
    return yieldTo(&amp;MyCoroutine::step2);
  }

  Action step2() {
    OATPP_LOGD(&quot;MyCoroutine&quot;, &quot;step2&quot;);
    return yieldTo(&amp;MyCoroutine::step3);
  }

  Action step3() {
    OATPP_LOGD(&quot;MyCoroutine&quot;, &quot;step3&quot;);
    return finish();
  }

};

oatpp::async::Executor executor();

executor.execute&lt;MyCoroutine&gt;();

executor.waitTasksFinished();
executor.stop();
executor.join();
</code></pre>
<p>输出为：</p>
<pre><code class="language-text-plain">MyCoroutine:act()
MyCoroutine:step2
MyCoroutine:step3
</code></pre>
<h3 id="从协程中调用协程"><a class="header" href="#从协程中调用协程">从协程中调用协程</a></h3>
<pre><code class="language-text-x-c-src">class OtherCoroutine : public oatpp::async::Coroutine&lt;OtherCoroutine&gt; {
public:
  Action act() override {
    OATPP_LOGD(&quot;OtherCoroutine&quot;, &quot;act()&quot;);
    return finish();
  }
};

class MyCoroutine : public oatpp::async::Coroutine&lt;MyCoroutine&gt; {
public:
  Action act() override {
    OATPP_LOGD(&quot;MyCoroutine&quot;, &quot;act()&quot;);
    return OtherCoroutine::start().next(finish());
    //Action将会在OtherCoroutine后被执行
    );
  }
};

oatpp::async::Executor executor();

executor.execute&lt;MyCoroutine&gt;();

executor.waitTasksFinished();
executor.stop();
executor.join();
</code></pre>
<p>输出：</p>
<pre><code class="language-text-x-c-src">MyCoroutine:act()
OtherCoroutine:act()
</code></pre>
<h3 id="调用协程并且返回结果"><a class="header" href="#调用协程并且返回结果">调用协程并且返回结果</a></h3>
<pre><code class="language-text-x-c-src">class CoroutineWithResult : 
	public oatpp::async::CoroutineWithResult&lt;CoroutineWithResult, const char* /*结果类型*/&gt; 
{
public:
  Action act() override {
    OATPP_LOGD(&quot;CoroutineWithResult&quot;, &quot;act()&quot;);
    return _return(&quot;&lt;result&gt;&quot;);
  }
};

class MyCoroutine : public oatpp::async::Coroutine&lt;MyCoroutine&gt; {
public:
  Action act() override {
    OATPP_LOGD(&quot;MyCoroutine&quot;, &quot;act()&quot;);
    return CoroutineWithResult::startForResult().callbackTo(&amp;MyCoroutine::onResult);
  }

  Action onResult(const char* result) {
    OATPP_LOGD(&quot;MyCoroutine&quot;, &quot;result='%s'&quot;, result);
    return finish();
  }
};

oatpp::async::Executor executor();

executor.execute&lt;MyCoroutine&gt;();

executor.waitTasksFinished();
executor.stop();
executor.join();
</code></pre>
<p>输出：</p>
<pre><code class="language-text-plain">MyCoroutine:act()
CoroutineWithResult:act()
MyCoroutine:result='&lt;result&gt;'
</code></pre>
<h3 id="计数器"><a class="header" href="#计数器">计数器</a></h3>
<pre><code class="language-text-x-c-src">class MyCoroutineCounter : public oatpp::async::Coroutine&lt;MyCoroutineCounter&gt; {
private:
  const char* m_name;
  v_int32 m_counter = 0;
public:
  MyCoroutineCounter(const char* name) : m_name(name) {}

  Action act() override {
    OATPP_LOGD(m_name, &quot;counter=%d&quot;, m_counter);
    if(m_counter &lt; 10) {
      m_counter ++;
      return repeat();
    }
    return finish();
  }

};

oatpp::async::Executor executor();

executor.execute&lt;MyCoroutineCounter&gt;(&quot;A&quot;);
executor.execute&lt;MyCoroutineCounter&gt;(&quot;B&quot;);
executor.execute&lt;MyCoroutineCounter&gt;(&quot;C&quot;);

executor.waitTasksFinished();
executor.stop();
executor.join();
</code></pre>
<p>一个可能的输出</p>
<pre><code class="language-text-plain">A:counter=0
B:counter=0
C:counter=0
A:counter=1
B:counter=1
C:counter=1
A:counter=2
B:counter=2
C:counter=2
A:counter=3
B:counter=3
C:counter=3
A:counter=4
B:counter=4
C:counter=4
A:counter=5
B:counter=5
C:counter=5
A:counter=6
B:counter=6
C:counter=6
A:counter=7
B:counter=7
C:counter=7
A:counter=8
B:counter=8
C:counter=8
A:counter=9
B:counter=9
C:counter=9
A:counter=10
B:counter=10
C:counter=10
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="关系型对象映射框架"><a class="header" href="#关系型对象映射框架">关系型对象映射框架</a></h1>
<p>Oat++ ORM框架采用广义的接口并且使数据库操作变得容易</p>
<p>这基于对象映射框架并且保证操作过程中的数据一致性。另外ORM能够很好的结合进其他Oat++组件，确保在应用程序中数据流的无缝衔接。（比如从REST到数据库，或者从数据库到REST）</p>
<h2 id="高层次api概览"><a class="header" href="#高层次api概览">高层次API概览</a></h2>
<h3 id="声明dbclient"><a class="header" href="#声明dbclient">声明DbClient</a></h3>
<p>你将使用的主要组件是DbClient，主要在该组件上处理数据，声明数据库请求并且管理数据库版本。</p>
<p>使用代码生成宏辅助生成数据库请求</p>
<p>DbClient代码创建块必须以<code>#include OATPP_CODEGEN_BEGIN(DbClient)</code> 并且必须用<code>#include OATPP_CODEGEN_END(DbClient)</code> 关闭代码块</p>
<p><em>不要忘记关闭代码生成块以防止在接下来的代码中出现宏定义冲突</em></p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/orm/SchemaMigration.hpp&quot;
#include &quot;oatpp/orm/DbClient.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;

#include OATPP_CODEGEN_BEGIN(DbClient) ///&lt; Begin code-gen section

class MyClient : public oatpp::orm::DbClient {
public:

  MyClient(const std::shared_ptr&lt;oatpp::orm::Executor&gt;&amp; executor)
    : oatpp::orm::DbClient(executor)
  {}
  
  QUERY(createUser,
        &quot;INSERT INTO users (username, email, role) VALUES (:username, :email, :role);&quot;,
        PARAM(oatpp::String, username), 
        PARAM(oatpp::String, email), 
        PARAM(oatpp::Enum&lt;UserRoles&gt;::AsString, role)) 

  QUERY(getUserByName, 
        &quot;SELECT * FROM users WHERE username=:username;&quot;, 
        PARAM(oatpp::String, username)) 
        
};

#include OATPP_CODEGEN_END(DbClient) ///&lt; End code-gen section
</code></pre>
<p><strong>创建DbClient组建并且连接到数据库</strong></p>
<p>DbClient是一个重量级的对象，一般只需要声明一次，然后在任何需要的地方引用即可</p>
<pre><code class="language-text-x-c-src">#include &quot;db/MyClient.hpp&quot;      //用户定义的DbClient
#include &quot;oatpp-sqlite/orm.hpp&quot; //&lt; 适用于Oat++ ORM的SQLite适配器

class AppComponent {
public:
  
  //创建DbCLient组建
  //这里使用SQLite作为例子，其他数据库的声明都是类似的
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;db::MyClient&gt;, myDatabaseClient)([] {

    //创建数据库模板特化的ConnectProvider
    auto connectionProvider = std::make_shared&lt;oatpp::sqlite::ConnectionProvider&gt;(&quot;/path/to/database.sqlite&quot;);    

    //创建数据库模板特化的执行器
    auto executor = std::make_shared&lt;oatpp::sqlite::Executor&gt;(connectionProvider);
  
    //创建MyClient数据库连接
    return std::make_shared&lt;MyClient&gt;(executor);

  }());

};
</code></pre>
<blockquote>
<p>ConnectionProvider和ConnectionPool对象可以被多个Executors重复使用，除非数据库的模板特化对象被禁止</p>
<p>Executor可以被多个DbClients重复使用，除非数据库的模板特化对象被禁止</p>
</blockquote>
<h3 id="dbclient使用例子"><a class="header" href="#dbclient使用例子">DbClient使用例子</a></h3>
<pre><code class="language-text-x-c-src">//添加MyClient的数据库连接
OATPP_COMPONENT(std::shared_ptr&lt;db::MyClient&gt;, client);

//在数据库中添加新用户
client-&gt;createUser(&quot;admin&quot;, &quot;admin@domain.com&quot;, UserRoles::ADMIN);

//在数据库中寻找用户名
auto result = client-&gt;getUserByUsername(&quot;admin&quot;);

//接收返回结果为UserDto模板类型的vector
//在此之前，UserDto已经被定义过了
//你也可以使用oatpp::Fields&lt;oatpp::Any&gt;来替代oatpp::Object&lt;UserDto&gt;接收任意类型的结果
auto dataset = result-&gt;fetch&lt;oatpp::Vector&lt;oatpp::Object&lt;UserDto&gt;&gt;&gt;();

//并且我们也可以很容易的使用JSON对象映射将结果序列化为JSON字符串
auto json = jsonObjectMapper.writeToString(dataset);

//打印JSON结果
std::cout &lt;&lt; json-&gt;c_str() &lt;&lt; std::endl;
</code></pre>
<p>输出</p>
<pre><code class="language-application-json">[
  {
    &quot;name&quot;: &quot;admin&quot;,
    &quot;email&quot;: &quot;admin@domain.com&quot;,
    &quot;role&quot;: &quot;ROLE_ADMIN&quot;
  }
]
</code></pre>
<h2 id="支持的数据库"><a class="header" href="#支持的数据库">支持的数据库</a></h2>
<h3 id="可用的数据库适配器"><a class="header" href="#可用的数据库适配器">可用的数据库适配器</a></h3>
<div class="table-wrapper"><table><thead><tr><th></th><th></th><th></th><th></th></tr></thead><tbody>
<tr><td>适配器</td><td>数据库</td><td>限制</td><td>示例项目</td></tr>
<tr><td>oatpp-sqlite</td><td>SQLite</td><td>全特性支持</td><td>example-crud</td></tr>
<tr><td>oatpp-postgresql</td><td>PostgreSQL</td><td>全部的postgres类型都不支持</td><td>example-postgresql</td></tr>
</tbody></table>
</div>
<h3 id="库目录树"><a class="header" href="#库目录树">库目录树</a></h3>
<p>主要的oatpp模块仅包含ORM接口，为了连接各种特定的数据库，你需要连接相关的适配器</p>
<pre><code class="language-application-json">- oatpp                     #主oatpp模块，ORM接口在这里
    |
    |- oatpp-sqlite         # Oatpp ORM的Sqlite适配器,Sqlite的模板特例化实现在这里
    |- oatpp-postgresql     # Oatpp ORM的PostgreSQL适配器,PostgreSQL的模板特化实现在这里
    ...
    ... etc.
</code></pre>
<h2 id="dbclient"><a class="header" href="#dbclient">DbClient</a></h2>
<h3 id="定义一个查询"><a class="header" href="#定义一个查询">定义一个查询</a></h3>
<pre><code class="language-text-x-c-src">QUERY(selectAllUsers, &quot;SELECT * FROM users;&quot;)
</code></pre>
<h3 id="带参数查询"><a class="header" href="#带参数查询">带参数查询</a></h3>
<pre><code class="language-text-x-c-src">QUERY(selectUserByUsername, 
      &quot;SELECT * FROM users WHERE username=:username;&quot;,
      PARAM(oatpp::String, username)) 
</code></pre>
<p>在执行过程中，表达式<code>username=:username</code> 将会被改为<code>username='&lt;username-parameter-value&gt;'</code> 并且参数的值将会</p>
<p>正确的转化为相符的类型。</p>
<h3 id="带dto参数的查询"><a class="header" href="#带dto参数的查询">带DTO参数的查询</a></h3>
<p>在复杂的请求中，使用DTO对象作为参数能带来很多便利，因此你需要确保正确的参数顺序</p>
<pre><code class="language-text-x-c-src">QUERY(insertUser, 
      &quot;INSERT INTO users &quot;
      &quot;(username, email, role) VALUES &quot;
      &quot;(:user.username, :user.email, :user.role);&quot;,
      PARAM(oatpp::Object&lt;UserDto&gt;, user))
</code></pre>
<blockquote>
<p>这个请求模板的变量名称从user开始</p>
<p>比如user.username中，user为DTO参数名称，username为DTO字段名称</p>
</blockquote>
<ul>
<li> 你可以使用具体的路径指定嵌套DTO字段，比如<code>:user.path.to.nested.field</code></li>
<li>你可以在请求中包含多个DTO参数，并且你也可以在普通的参数中混合DTO参数</li>
</ul>
<h3 id="带预编译语句的请求"><a class="header" href="#带预编译语句的请求">带预编译语句的请求</a></h3>
<pre><code class="language-text-x-c-src">QUERY(selectUserByUsername, 
      &quot;SELECT * FROM users WHERE username=:username;&quot;,
      PREPARE(true), //设置prepare为true以使用预编译语句
      PARAM(oatpp::String, username)) 
</code></pre>
<blockquote>
<p>数据库适配器可能会忽略这个语句，比如说：</p>
<ul>
<li>SQLite一直使用预编译语句执行请求，因此oatpp-sqlite会忽略PREPARE参数</li>
<li>PostgreSQL采用特殊的方法去执行预编译语句，因此oatpp-postgresql将不会忽略PREPARE参数</li>
</ul>
</blockquote>
<h3 id="执行一个任意的查询"><a class="header" href="#执行一个任意的查询">执行一个任意的查询</a></h3>
<p>执行一个任意的查询使用<code>DbClient::executeQuery()</code> 这个方法</p>
<p>当需要构建一个动态的查询就使用这个方法</p>
<pre><code class="language-text-x-c-src">auto dbResult = client.executeQuery(&quot;SELECT * FROM users;&quot;, {} /* 空参数表 */);
</code></pre>
<p>你可以使用参数表添加参数</p>
<pre><code class="language-text-x-c-src">auto dbResult = client.executeQuery(
  &quot;SELECT * FROM users WHERE id=:id AND username=:username;&quot;, 
  {
    {&quot;id&quot;, oatpp::Int64(23)},             //你可以直接在这里指定参数类型 - oatpp::Int64
    {&quot;username&quot;, oatpp::String(&quot;admin&quot;)}
  }
);
</code></pre>
<p>当需要动态构建参数表时可以使用<code>std::unordered_map::insert()</code> 方法，<code>[]</code> 操作在这里不可用</p>
<pre><code class="language-text-x-c-src">std::unordered_map&lt;oatpp::String, oatpp::Void&gt; params;
params.insert({&quot;id&quot;, oatpp::Int64(23)});
params.insert({&quot;username&quot;, oatpp::String(&quot;admin&quot;)});
auto dbResult = client.executeQuery(&quot;SELECT * FROM users WHERE id=:id AND username=:username;&quot;, params);
</code></pre>
<p>构建查询字符串推荐使用<code>oatpp::data::stream::BufferOutputStream</code></p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/core/data/stream/BufferStream.hpp&quot;

...

oatpp::data::stream::BufferOutputStream stream;
stream 
&lt;&lt; &quot;SELECT * FROM users &quot;
&lt;&lt; &quot;WHERE &quot;
&lt;&lt; &quot;id=:id&quot; &lt;&lt; &quot; AND &quot; &lt;&lt; &quot;username=:username&quot; &lt;&lt; &quot;;&quot; 

std::unordered_map&lt;oatpp::String, oatpp::Void&gt; params;
params.insert({&quot;id&quot;, oatpp::Int64(23)});
params.insert({&quot;username&quot;, oatpp::String(&quot;admin&quot;)});

auto dbResult = client.executeQuery(stream.toString(), params);
</code></pre>
<h3 id="启用类型解释type-interpretations"><a class="header" href="#启用类型解释type-interpretations">启用类型解释（Type Interpretations）</a></h3>
<p>当在<code>QUERY</code> 宏参数列表中使用自定义或非标准类型或者在读取查询的结果是自定义或非标准结构，你应该明确的使用相应的类型解释</p>
<p>一个推荐的做法是使用构造函数</p>
<pre><code class="language-text-x-c-src">class MyClient : public oatpp::orm::DbClient {
public:

  MyClient(const std::shared_ptr&lt;oatpp::orm::Executor&gt;&amp; executor)
    : oatpp::orm::DbClient(executor)
  {
    setEnabledInterpretations({&quot;protobuf&quot;});
  }

  ...
        
};
</code></pre>
<p><strong>采用自定义类型参数的查询</strong></p>
<pre><code class="language-text-x-c-src">QUERY(insertUser, 
      &quot;INSERT INTO users &quot;
      &quot;(username, email, role) VALUES &quot;
      &quot;(:user.username, :user.email, :user.role);&quot;,
      PARAM(oatpp::protobuf::Object&lt;User&gt;, user)) //通过protobuf对象
</code></pre>
<p><strong>映射查询结果到自定义类型</strong></p>
<pre><code class="language-text-x-c-src">/*执行查询*/
auto result = client-&gt;getUserByUsername(&quot;admin&quot;);

/*映射结果到protobuf对象到vector容器中*/
auto dataset = res-&gt;fetch&lt;oatpp::Vector&lt;oatpp::protobuf::Object&lt;User&gt;&gt;&gt;(); // Map result

for(auto&amp; user : *dataset) {
  ...
}
</code></pre>
<p><strong>处理</strong></p>
<p>使用<code>DbClient::beginTransaction()</code> 方法开始处理</p>
<p>所有的请求必须被同一个处理连接执行</p>
<pre><code class="language-text-x-c-src">{
  auto transaction = client.beginTransaction();
  
  client.insertUser(user1, transaction.getConnection());
  client.insertUser(user2, transaction.getConnection());
  client.insertUser(user3, transaction.getConnection());

  transaction.commit();
}
</code></pre>
<blockquote>
<p>如果<code>Transaction::commit()</code> 没有被调用，那么这些处理将会被自动回滚</p>
</blockquote>
<h2 id="执行查询"><a class="header" href="#执行查询">执行查询</a></h2>
<pre><code class="language-text-x-c-src">/* Execute a query */
auto queryResult = client.selectAllUsers();

/* Check if the operation was successful */
if(!queryResult-&gt;isSuccess()) {
  auto message = queryResult-&gt;getErrorMessage();
  OATPP_LOGD(&quot;Query&quot;, &quot;Error, message=%s&quot;, message-&gt;c_str());
}

/* Fetch everything as a vector of User objects */
auto dataset = queryResult-&gt;fetch&lt;oatpp::Vector&lt;oatpp::Object&lt;User&gt;&gt;&gt;();
</code></pre>
<p><code>queryResult</code> 在这里是<code>oatpp::orm::QueryResult</code> 对象，所有的请求都会返回<code>oatpp::orm::QueryResult</code></p>
<h3 id="映射请求"><a class="header" href="#映射请求">映射请求</a></h3>
<p>可用的请求映射取决于数据库适配器，但是这里有一些例子（这些例子能够工作在oatpp-sqlite和oatpp-postgresql）</p>
<p>无论映射什么都需要提前声明<code>UserDto</code> 并显示结果</p>
<p>更多关于声明DTO的信息，参见<a href="https://oatpp.io/docs/components/dto/">oatpp::DTO</a></p>
<pre><code class="language-text-x-c-src">auto dataset = queryResult-&gt;fetch&lt;oatpp::Vector&lt;oatpp::Object&lt;oatpp::UserDto&gt;&gt;&gt;();

/* Serialize result as a json string using json object mapper */
auto json = jsonObjectMapper.writeToString(dataset);

/* Print the resultant json */
std::cout &lt;&lt; json-&gt;c_str() &lt;&lt; std::endl;
</code></pre>
<p>输出</p>
<pre><code class="language-text-x-c-src">[
  {
    &quot;name&quot;: &quot;admin&quot;,
    &quot;email&quot;: &quot;admin@domain.com&quot;,
    &quot;role&quot;: &quot;ROLE_ADMIN&quot;
  },
  {
    &quot;name&quot;: &quot;ivan&quot;,
    &quot;email&quot;: &quot;ivan@domain.com&quot;,
    &quot;role&quot;: &quot;ROLE_GUEST&quot;
  }
]
</code></pre>
<p>使用<code>oatpp::Any</code> 映射所有数据并且显示结果</p>
<pre><code class="language-text-x-c-src">auto dataset = queryResult-&gt;fetch&lt;oatpp::Vector&lt;oatpp::Fields&lt;oatpp::Any&gt;&gt;&gt;();

/* Serialize result as a json string using json object mapper */
auto json = jsonObjectMapper.writeToString(dataset);

/* Print the resultant json */
std::cout &lt;&lt; json-&gt;c_str() &lt;&lt; std::endl;
</code></pre>
<p>输出</p>
<pre><code class="language-text-x-c-src">[
  {
    &quot;name&quot;: &quot;admin&quot;,
    &quot;email&quot;: &quot;admin@domain.com&quot;,
    &quot;role&quot;: &quot;ROLE_ADMIN&quot;
  },
  {
    &quot;name&quot;: &quot;ivan&quot;,
    &quot;email&quot;: &quot;ivan@domain.com&quot;,
    &quot;role&quot;: &quot;ROLE_GUEST&quot;
  }
]
</code></pre>
<h3 id="管理连接"><a class="header" href="#管理连接">管理连接</a></h3>
<p>所有声明的请求最后一个参数都是<code>oatpp::orm::Connection</code></p>
<p>如果没有指定连接（<code>nullptr</code>）那么新的连接将会在执行请求时打开</p>
<pre><code class="language-text-x-c-src">{
  auto queryResult = client.selectAllUsers(); //&lt; Open a new connection.
}

{
  auto connection = client.getConnection();
  auto queryResult = client.selectAllUsers(connection); //&lt; Execute using the connection provided.
}

{
  auto queryResult = client.selectAllUsers(); //&lt; Open a new connection.
  ...
  queryResult = client.insertUser(user, queryResult-&gt;getConnection()); //&lt; Execute on the same connection as the last query.
}
</code></pre>
<blockquote>
<p>queryResult会保持一个连接，这个连接不会返回到连接池中，除非queryResult被销毁了</p>
</blockquote>
<h2 id="连接池"><a class="header" href="#连接池">连接池</a></h2>
<p>当使用数据库时，这里有一个好的办法去使用连接池</p>
<pre><code class="language-text-x-c-src">#include &quot;db/MyClient.hpp&quot;
#include &quot;oatpp-sqlite/orm.hpp&quot;

class AppComponent {
public:
  
  /**
   * Create DbClient component.
   * SQLite is used as an example here. For other databases declaration is similar.
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;db::MyClient&gt;, myDatabaseClient)([] {
    /* Create database-specific ConnectionProvider */
    auto connectionProvider = std::make_shared&lt;oatpp::sqlite::ConnectionProvider&gt;(&quot;/path/to/database.sqlite&quot;);    
  
    /* Create database-specific ConnectionPool */
    auto connectionPool = oatpp::sqlite::ConnectionPool::createShared(connectionProvider, 
                                                                      10 /* max-connections */, 
                                                                      std::chrono::seconds(5) /* connection TTL */);
    
    /* Create database-specific Executor */
    auto executor = std::make_shared&lt;oatpp::sqlite::Executor&gt;(connectionPool);
  
    /* Create MyClient database client */
    return std::make_shared&lt;MyClient&gt;(executor);
  }());

};
</code></pre>
<blockquote>
<p>这个例子中使用的是SQLite，其他数据库的声明都是类似的</p>
</blockquote>
<h2 id="schema-migration"><a class="header" href="#schema-migration">Schema Migration</a></h2>
<p>使用<a href="https://oatpp.io/api/latest/oatpp/orm/SchemaMigration/">SchemaMigration</a> 完成结构迁移</p>
<p>推荐的做法是使用Schema Migration构造DbClient</p>
<h3 id="概览"><a class="header" href="#概览">概览</a></h3>
<pre><code class="language-text-x-c-src">class MyClient : public oatpp::orm::DbClient {
public:

  MyClient(const std::shared_ptr&lt;oatpp::orm::Executor&gt;&amp; executor)
    : oatpp::orm::DbClient(executor)
  {
    oatpp::orm::SchemaMigration migration(executor); 
    migration.addFile(1 /* version */, &quot;sql/initial_schema.sql&quot; /* migration script */);
    migration.addFile(2 /* version */, &quot;sql/schema_fix_1.sql&quot;   /* migration script */);
    ...
    migration.migrate(); //&lt;-- This guy will throw on error.
  }

  ...
        
};
</code></pre>
<blockquote>
<ul>
<li>版本必须从1开始</li>
<li>版本必须从1开始增加</li>
<li>如果有错误发生，将会回滚到最后一次成功应用的版本</li>
</ul>
</blockquote>
<h3 id="schema名称"><a class="header" href="#schema名称">Schema名称</a></h3>
<p>如果你有多个Schemans在你的数据库中，你可以单独的管理每一个migrations，采用指定的版本控制前缀：</p>
<pre><code class="language-text-x-c-src">oatpp::orm::SchemaMigration migration(executor, &quot;suffix&quot;);
</code></pre>
<blockquote>
<p>推荐每个schema都有一个DbClient</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="monolithization"><a class="header" href="#monolithization">Monolithization</a></h1>
<p>Monolithization是一项能让你更好的扩展收缩微型服务器规模的技术。在Oat++中你可以开发一个普通的微型服务器，并且当这个微服务器规模小到在你系统上不会有明显负载时，你可以将这些服务合并为一个monolith。此外，合并服务器可以带来更好的性能并且消耗更少的内存。因此你可以通过降低网络负载和减少运行实例的数量进而减少基础设置的开销。</p>
<p>最好理解Monolithization和它如何工作的方式是通过例子</p>
<h2 id="example"><a class="header" href="#example">Example</a></h2>
<p>考虑以下由三个零散的微服务器组成的系统</p>
<ul>
<li>User Service</li>
<li>Book Service</li>
<li>Facade</li>
</ul>
<p><img src="Monolithization/image.png" alt="" /></p>
<p>Facade请求user信息从User-Service，book信息从Book-Service。然后合并数据并且返回给客户端</p>
<h2 id="问题"><a class="header" href="#问题">问题</a></h2>
<p>当在系统中只有一点负载的时候，就没有必要缩放这个部分。因此一个更好的做法是将这个系统作为一个monolith，而不是安排多个实例或者容器。同时为了保持灵活的特性，你依然需要保持微服务架构。</p>
<p>你也许将所有服务部署在运行于单个处理器的单个实例中。这种情况或许是附加了devops的结果，通过localhost传输数据会降低性能，而且你依然有网络方面的开销。</p>
<h2 id="monolithization的观念"><a class="header" href="#monolithization的观念">Monolithization的观念</a></h2>
<p>这个观念是简单的，我们将会把所有服务运行在单个处理器上，并且这些服务将会通过Oat++虚连接进行通信</p>
<p>使用Oat++虚连接替代网络传输层，因此你的应用程序逻辑可以独立出来，并且他们利用内部缓冲而不是通过localhost通信，因此没有网络方面的开销</p>
<h3 id="服务接口"><a class="header" href="#服务接口">服务接口</a></h3>
<p>一致的服务可以保持他们的网络接口，并且对远程服务器是可用的。并且他们通过统一的服务通过虚连接暴露一致的接口</p>
<h3 id="构建管道pipeline"><a class="header" href="#构建管道pipeline">构建管道(pipeline)</a></h3>
<p>一个好事情是你不需要改变你构建的管道，每一个团队都可以独立的开发他们自己的服务器。</p>
<p>如果你需要统一这些服务，你需要为每一个monolithic创建一份配置</p>
<h3 id="缩放"><a class="header" href="#缩放">缩放</a></h3>
<p>通过Monolithization，你可以渐进式缩放服务，一步一步添加新的monolithic部署，并且去耦合成更小的快，最终使你的云服务器达到最佳的配置。</p>
<h2 id="性能"><a class="header" href="#性能">性能</a></h2>
<p>以下是性能测试，通过对比Oat++微服务(部署在相同的实例上)和通过Monolithization合并的同样的微服务器性能</p>
<blockquote>
<p>这里原先有图的，现在不知道为啥图打不开了</p>
</blockquote>
<h3 id="请求时间"><a class="header" href="#请求时间">请求时间</a></h3>
<blockquote>
<p>图丢了</p>
</blockquote>
<h3 id="内存占用mb"><a class="header" href="#内存占用mb">内存占用(MB)</a></h3>
<div class="table-wrapper"><table><thead><tr><th></th><th>Facade</th><th>User Service</th><th>Book Service</th><th>Total</th></tr></thead><tbody>
<tr><td>Oat++ (micro-svc) idle</td><td>25</td><td>3</td><td>3</td><td>31</td></tr>
<tr><td>Oat++ (micro-svc) load</td><td>45</td><td>3.2</td><td>3.2</td><td>49.4</td></tr>
<tr><td>Oat++ (monolith) idle</td><td>-</td><td>-</td><td>-</td><td>26.7</td></tr>
<tr><td>Oat++ (monolith) load</td><td>-</td><td>-</td><td>-</td><td>37.7</td></tr>
</tbody></table>
</div><div style="break-before: page; page-break-before: always;"></div><h1 id="步骤指南"><a class="header" href="#步骤指南">步骤指南</a></h1>
<p>这份步骤指南将会帮助你使用oatpp框架进行开发。按照指南完成后你将会得到一个具有良好结构和可扩展性，并且拥有一个基本节点的web服务器</p>
<h2 id="最简单的项目"><a class="header" href="#最简单的项目">最简单的项目</a></h2>
<h3 id="api-低级别组件概览"><a class="header" href="#api-低级别组件概览">API-低级别组件概览</a></h3>
<p>先来看一个最简单的oatpp服务器应用</p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

void run() {

  /* Create Router for HTTP requests routing */
  auto router = oatpp::web::server::HttpRouter::createShared();

  /* Create HTTP connection handler with router */
  auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

  /* Create TCP connection provider */
  auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Print info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<p>采用的类：</p>
<ul>
<li>HttpRouter - 对HTTP请求进行路由。这会映射URL到Endpoint处理函数上。我们的app没有声明endpoints，因此到目前为止，任何请求都会返回 <code>404 not found</code></li>
<li>HttpConnectionHandler - 这是一个简单的HTTP连接处理函数。这会采用多线程处理由客户端发起的连接，每一个连接都会创建一个线程</li>
<li>ConnectionProvider - 提供TCP连接，并且会绑定指定的端口</li>
<li>Server - Server在一个循环中从ConnectProvider接受连接，传递给ConnectionHandler</li>
</ul>
<h3 id="添加请求处理函数"><a class="header" href="#添加请求处理函数">添加请求处理函数</a></h3>
<p>应用程序目前不会做任何事情，所有的连接都会响应404，我们需要添加HttpRequestHandler并且通过Router路由请求来添加自定义响应。</p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

/** 
 * Custom Request Handler
 */
class Handler : public oatpp::web::server::HttpRequestHandler {
public:

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr&lt;OutgoingResponse&gt; handle(const std::shared_ptr&lt;IncomingRequest&gt;&amp; request) override {
    return ResponseFactory::createResponse(Status::CODE_200, &quot;Hello World!&quot;);
  }

};

void run() {

  /* Create Router for HTTP requests routing */
  auto router = oatpp::web::server::HttpRouter::createShared();
  
  /* Route GET - &quot;/hello&quot; requests to Handler */
  router-&gt;route(&quot;GET&quot;, &quot;/hello&quot;, std::make_shared&lt;Handler&gt;());

  /* Create HTTP connection handler with router */
  auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

  /* Create TCP connection provider */
  auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<p>现在如果我们在游览器中打开<code>http:/localhost:8000/hello</code> 我们将会看到 <code>Hello World!</code> 消息</p>
<pre><code class="language-text-plain">$ curl http://localhost:8000/hello
Hello World!
</code></pre>
<h3 id="使用json对象响应请求"><a class="header" href="#使用json对象响应请求">使用JSON对象响应请求</a></h3>
<p>为了序列化/反序列化对象，oatpp使用从Data-Transfer-Objects(DTO)和ObjectMappers继承的对象</p>
<p>DTO对象序列化的格式被定义在Object Mapper中</p>
<p>这个特例我们使用JSON ObjectMapper，所以我们的数据将会被序列化为JSON</p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;

#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin DTO code-generation */
#include OATPP_CODEGEN_BEGIN(DTO)

/**
 * Message Data-Transfer-Object
 */
class MessageDto : public oatpp::DTO {

  DTO_INIT(MessageDto, DTO /* Extends */)

  DTO_FIELD(Int32, statusCode);   // Status code field
  DTO_FIELD(String, message);     // Message field

};

/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

/**
 * Custom Request Handler
 */
class Handler : public oatpp::web::server::HttpRequestHandler {
private:
  std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt; m_objectMapper;
public:

  /**
   * Constructor with object mapper.
   * @param objectMapper - object mapper used to serialize objects.
   */
  Handler(const std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;&amp; objectMapper)
    : m_objectMapper(objectMapper)
  {}

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr&lt;OutgoingResponse&gt; handle(const std::shared_ptr&lt;IncomingRequest&gt;&amp; request) override {
    auto message = MessageDto::createShared();
    message-&gt;statusCode = 1024;
    message-&gt;message = &quot;Hello DTO!&quot;;
    return ResponseFactory::createResponse(Status::CODE_200, message, m_objectMapper);
  }

};

void run() {

  /* Create json object mapper */
  auto objectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared();

  /* Create Router for HTTP requests routing */
  auto router = oatpp::web::server::HttpRouter::createShared();

  /* Route GET - &quot;/hello&quot; requests to Handler */
  router-&gt;route(&quot;GET&quot;, &quot;/hello&quot;, std::make_shared&lt;Handler&gt;(objectMapper /* json object mapper */ ));

  /* Create HTTP connection handler with router */
  auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

  /* Create TCP connection provider */
  auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<p>现在我们在游览器中打开<code>http:/localhost:8000/hello</code> 我们将会获得JSON格式的响应</p>
<pre><code class="language-text-plain">$ curl http://localhost:8000/hello
{&quot;statusCode&quot;: 1024, &quot;message&quot;: &quot;Hello DTO!&quot;}
</code></pre>
<h2 id="良好的工程结构"><a class="header" href="#良好的工程结构">良好的工程结构</a></h2>
<p>为所有的自定义节点创建请求处理函数对于小型项目而言是合适的。这里建议遵循oatpp项目结构，这样可以更容易配置和扩展代码</p>
<h3 id="工程结构"><a class="header" href="#工程结构">工程结构</a></h3>
<pre><code class="language-text-plain">|- CMakeLists.txt                        // projects CMakeLists.txt
|- src/
|    |
|    |- dto/                             // Folder containing DTOs definitions
|    |    |
|    |    |- DTOs.hpp                    // DTOs are declared here
|    |     
|    |- controller/                      // Folder containing API Controllers where all endpoints are declared
|    |    |
|    |    |- MyController.hpp            // Sample - MyController is declared here
|    |     
|    |- AppComponent.hpp                 // Application Components configuration 
|    |- App.cpp                          // main() is here
|
|- test/                                 // test folder
     |
     |- app/
     |    |
     |    |- MyApiTestClient.hpp         // Api client for test API calls is declared here
     |    |- TestComponent.hpp           // Test application components configuration
     |                                   
     |- MyControllerTest.cpp             // MyController test logic is here
     |- MyControllerTest.hpp             // MyController test header
     |- Tests.cpp                        // tests main() is here
</code></pre>
<h3 id="应用程序组件配置"><a class="header" href="#应用程序组件配置">应用程序组件配置</a></h3>
<p>使用Oatpp提供的依赖配置你的应用程序</p>
<p>通过OATPP_CREATE_COMPONENT创建环境组件，在需要的时候使用OATPP_COMPONENT注入组件</p>
<h4 id="使用注入依赖对简单的例子进行修改"><a class="header" href="#使用注入依赖对简单的例子进行修改">使用注入依赖对简单的例子进行修改</a></h4>
<p>在你工程的src文件夹下创建AppComponent.hpp文件，在这文件里对所有的应用程序组件进行初始化</p>
<p><code>AppComponent.hpp</code></p>
<pre><code class="language-text-x-c-src">#ifndef AppComponent_hpp
#define AppComponent_hpp

#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;

#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

#include &quot;oatpp/core/macro/component.hpp&quot;

/**
 *  Class which creates and holds Application components and registers components in oatpp::base::Environment
 *  Order of components initialization is from top to bottom
 */
class AppComponent {
public:

  /**
   *  Create ConnectionProvider component which listens on the port
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, serverConnectionProvider)([] {
    return oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});
  }());

  /**
   *  Create Router component
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, httpRouter)([] {
    return oatpp::web::server::HttpRouter::createShared();
  }());

  /**
   *  Create ConnectionHandler component which uses Router component to route requests
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, serverConnectionHandler)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router); // get Router component
    return oatpp::web::server::HttpConnectionHandler::createShared(router);
  }());

  /**
   *  Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, apiObjectMapper)([] {
    return oatpp::parser::json::mapping::ObjectMapper::createShared();
  }());

};

#endif /* AppComponent_hpp */
</code></pre>
<p>现在所有主要的组件都初始化在一个地方了，这样在替换组件的时候配置应用程序就很方便了</p>
<p><code>App.cpp</code> 可以这样重写</p>
<pre><code class="language-text-x-c-src">#include &quot;AppComponent.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;

#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin DTO code-generation */
#include OATPP_CODEGEN_BEGIN(DTO)

/**
 * Message Data-Transfer-Object
 */
class MessageDto : public oatpp::DTO {

  DTO_INIT(MessageDto, DTO /* Extends */)

  DTO_FIELD(Int32, statusCode);   // Status code field
  DTO_FIELD(String, message);     // Message field

};

/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

/**
 * Custom Request Handler
 */
class Handler : public oatpp::web::server::HttpRequestHandler {
private:
  /* Inject object mapper component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, m_objectMapper);
public:

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr&lt;OutgoingResponse&gt; handle(const std::shared_ptr&lt;IncomingRequest&gt;&amp; request) override {
    auto message = MessageDto::createShared();
    message-&gt;statusCode = 1024;
    message-&gt;message = &quot;Hello DTO!&quot;;
    return ResponseFactory::createResponse(Status::CODE_200, message, m_objectMapper);
  }

};

void run() {

  /* Register Components in scope of run() method */
  AppComponent components;

  /* Get router component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router);

  /* Route GET - &quot;/hello&quot; requests to Handler */
  router-&gt;route(&quot;GET&quot;, &quot;/hello&quot;, std::make_shared&lt;Handler&gt;());

  /* Get connection handler component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, connectionHandler);

  /* Get connection provider component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, connectionProvider);

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<h4 id="移动dto定义到一个单独的文件中"><a class="header" href="#移动dto定义到一个单独的文件中">移动DTO定义到一个单独的文件中</a></h4>
<p>在文件夹<code>src/dto/</code> 创建文件 <code>DTOs.hpp</code></p>
<p>移动<code>MessageDto</code> 定义到<code>DTOs.hpp</code></p>
<pre><code class="language-text-x-c-src">#ifndef DTOs_hpp
#define DTOs_hpp

#include &quot;oatpp/core/data/mapping/type/Object.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin DTO code-generation */
#include OATPP_CODEGEN_BEGIN(DTO)

/**
 * Message Data-Transfer-Object
 */
class MessageDto : public oatpp::DTO {

  DTO_INIT(MessageDto, DTO /* Extends */)

  DTO_FIELD(Int32, statusCode);   // Status code field
  DTO_FIELD(String, message);     // Message field

};

/* TODO - Add more DTOs here */

/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

#endif /* DTOs_hpp */
</code></pre>
<h4 id="使用api-controller"><a class="header" href="#使用api-controller">使用API Controller</a></h4>
<p>代替单独使用HttpRequestHandler为所有节点创建新的请求处理函数，这里推荐使用Api Controller</p>
<p>API Controller 使用代码创建模板使得添加新节点的过程变得容易，这可以帮助你在不同的API Controller中组织你的节点</p>
<h5 id="创建api-controller"><a class="header" href="#创建api-controller">创建API Controller</a></h5>
<p>在文件夹<code>src/controller/</code> 创建文件<code>MyController.hpp</code></p>
<p>添加下面的代码到<code>MyController.hpp</code> 中</p>
<pre><code class="language-text-x-c-src">#ifndef MyController_hpp
#define MyController_hpp

#include &quot;dto/DTOs.hpp&quot;

#include &quot;oatpp/web/server/api/ApiController.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;
#include &quot;oatpp/core/macro/component.hpp&quot;

#include OATPP_CODEGEN_BEGIN(ApiController) ///&lt; Begin Codegen

/**
 * Sample Api Controller.
 */
class MyController : public oatpp::web::server::api::ApiController {
public:
  /**
   * Constructor with object mapper.
   * @param objectMapper - default object mapper used to serialize/deserialize DTOs.
   */
  MyController(OATPP_COMPONENT(std::shared_ptr&lt;ObjectMapper&gt;, objectMapper))
    : oatpp::web::server::api::ApiController(objectMapper)
  {}
public:
  
  ENDPOINT(&quot;GET&quot;, &quot;/hello&quot;, root) {
    auto dto = MessageDto::createShared();
    dto-&gt;statusCode = 200;
    dto-&gt;message = &quot;Hello World!&quot;;
    return createDtoResponse(Status::CODE_200, dto);
  }
  
  // TODO Insert Your endpoints here !!!
  
};

#include OATPP_CODEGEN_END(ApiController) ///&lt; End Codegen

#endif /* MyController_hpp */
</code></pre>
<h4 id="添加控制节点到路由中"><a class="header" href="#添加控制节点到路由中">添加控制节点到路由中</a></h4>
<p>为了在Api Controller中声明的节点能够工作，我们需要添加这些节点到路由中</p>
<p>最终App.cpp看起来是这样的</p>
<pre><code class="language-text-x-c-src">#include &quot;controller/MyController.hpp&quot;
#include &quot;AppComponent.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;

void run() {

  /* Register Components in scope of run() method */
  AppComponent components;

  /* Get router component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router);

  /* Create MyController and add all of its endpoints to router */
  auto myController = std::make_shared&lt;MyController&gt;();
  router-&gt;addController(myController);

  /* Get connection handler component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, connectionHandler);

  /* Get connection provider component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, connectionProvider);

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
  
}

int main(int argc, const char * argv[]) {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;
}
</code></pre>
<h2 id="测试oat应用程序"><a class="header" href="#测试oat应用程序">测试Oat++应用程序</a></h2>
<p>测试一个Oat++程序通常需要以下几个步骤：</p>
<ol>
<li>创建应用程序测试配置文件，以便让应用程序运行在测试模式中</li>
<li>为应用程序的Api定义ApiClient</li>
<li>创建测试，让应用程序使用测试配置文件运行，让API调用API Client</li>
</ol>
<h3 id="应用程序测试组建配置"><a class="header" href="#应用程序测试组建配置">应用程序测试组建配置</a></h3>
<p>在文件夹<code>test/app/</code> 中创建文件<code>TestComponent.hpp</code> （类似于AppComponent）</p>
<pre><code class="language-text-x-c-src">#ifndef TestComponent_htpp
#define TestComponent_htpp

#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/virtual_/client/ConnectionProvider.hpp&quot;
#include &quot;oatpp/network/virtual_/server/ConnectionProvider.hpp&quot;
#include &quot;oatpp/network/virtual_/Interface.hpp&quot;

#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;

#include &quot;oatpp/core/macro/component.hpp&quot;

/**
 * Test Components config
 */
class TestComponent {
public:

  /**
   * Create oatpp virtual network interface for test networking
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::virtual_::Interface&gt;, virtualInterface)([] {
    return oatpp::network::virtual_::Interface::obtainShared(&quot;virtualhost&quot;);
  }());

  /**
   * Create server ConnectionProvider of oatpp virtual connections for test
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, serverConnectionProvider)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::virtual_::Interface&gt;, interface);
    return oatpp::network::virtual_::server::ConnectionProvider::createShared(interface);
  }());

  /**
   * Create client ConnectionProvider of oatpp virtual connections for test
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ClientConnectionProvider&gt;, clientConnectionProvider)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::virtual_::Interface&gt;, interface);
    return oatpp::network::virtual_::client::ConnectionProvider::createShared(interface);
  }());

  /**
   *  Create Router component
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, httpRouter)([] {
    return oatpp::web::server::HttpRouter::createShared();
  }());

  /**
   *  Create ConnectionHandler component which uses Router component to route requests
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, serverConnectionHandler)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router); // get Router component
    return oatpp::web::server::HttpConnectionHandler::createShared(router);
  }());

  /**
   *  Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, apiObjectMapper)([] {
    return oatpp::parser::json::mapping::ObjectMapper::createShared();
  }());

};


#endif // TestComponent_htpp
</code></pre>
<p>注意这里使用了：</p>
<ul>
<li><a href="https://oatpp.io/api/latest/oatpp/network/virtual_/Interface/">Network Virtual Interface</a></li>
<li><a href="https://oatpp.io/api/latest/oatpp/network/virtual_/server/ConnectionProvider/">Server VIrtual ConnectionProvider</a></li>
<li><a href="https://oatpp.io/api/latest/oatpp/network/virtual_/client/ConnectionProvider/">Client Virtual ConnectionProvider</a></li>
</ul>
<p>oatpp虚拟网络栈能够让你运行应用程序测试在底层协议上，并不需要占用真实的主机端口</p>
<h3 id="api-test-client"><a class="header" href="#api-test-client">API Test Client</a></h3>
<p>创建测试用途的ApiClient以便测试应用程序API</p>
<p>在文件夹<code>test/app/</code>创建文件<code>MyApiTestClient.hpp</code> 使用api调用相应的应用程序API</p>
<pre><code class="language-text-x-c-src">#ifndef MyApiTestClient_hpp
#define MyApiTestClient_hpp

#include &quot;oatpp/web/client/ApiClient.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin Api Client code generation */
#include OATPP_CODEGEN_BEGIN(ApiClient)

/**
 * Test API client.
 * Use this client to call application APIs.
 */
class MyApiTestClient : public oatpp::web::client::ApiClient {

  API_CLIENT_INIT(MyApiTestClient)

  API_CALL(&quot;GET&quot;, &quot;/hello&quot;, getHello)

  // TODO - add more client API calls here

};

/* End Api Client code generation */
#include OATPP_CODEGEN_END(ApiClient)

#endif // MyApiTestClient_hpp
</code></pre>
<h3 id="创建测试"><a class="header" href="#创建测试">创建测试</a></h3>
<p>在文件夹<code>test/</code> 下创建文件<code>MyControllerTest.hpp</code></p>
<pre><code class="language-text-x-c-src">#ifndef MyControllerTest_hpp
#define MyControllerTest_hpp

#include &quot;oatpp-test/UnitTest.hpp&quot;

class MyControllerTest : public oatpp::test::UnitTest {
public:

  MyControllerTest() : UnitTest(&quot;TEST[MyControllerTest]&quot; /* Test TAG for logs */){}
  void onRun() override;

};

#endif // MyControllerTest_hpp
</code></pre>
<p>在文件夹<code>test/</code> 创建文件<code>MyControllerTest.cpp</code></p>
<pre><code class="language-text-x-c-src">#include &quot;MyControllerTest.hpp&quot;

#include &quot;controller/MyController.hpp&quot;

#include &quot;app/MyApiTestClient.hpp&quot;
#include &quot;app/TestComponent.hpp&quot;

#include &quot;oatpp/web/client/HttpRequestExecutor.hpp&quot;

#include &quot;oatpp-test/web/ClientServerTestRunner.hpp&quot;

void MyControllerTest::onRun() {

  /* Register test components */
  TestComponent component;

  /* Create client-server test runner */
  oatpp::test::web::ClientServerTestRunner runner;

  /* Add MyController endpoints to the router of the test server */
  runner.addController(std::make_shared&lt;MyController&gt;());

  /* Run test */
  runner.run([this, &amp;runner] {

    /* Get client connection provider for Api Client */
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ClientConnectionProvider&gt;, clientConnectionProvider);

    /* Get object mapper component */
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, objectMapper);

    /* Create http request executor for Api Client */
    auto requestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(clientConnectionProvider);

    /* Create Test API client */
    auto client = MyApiTestClient::createShared(requestExecutor, objectMapper);

    /* Call server API */
    /* Call hello endpoint of MyController */
    auto response = client-&gt;getHello();

    /* Assert that server responds with 200 */
    OATPP_ASSERT(response-&gt;getStatusCode() == 200);

    /* Read response body as MessageDto */
    auto message = response-&gt;readBodyToDto&lt;oatpp::Object&lt;MessageDto&gt;&gt;(objectMapper.get());

    /* Assert that received message is as expected */
    OATPP_ASSERT(message);
    OATPP_ASSERT(message-&gt;statusCode-&gt;getValue() == 200);
    OATPP_ASSERT(message-&gt;message == &quot;Hello World!&quot;);

  }, std::chrono::minutes(10) /* test timeout */);

  /* wait all server threads finished */
  std::this_thread::sleep_for(std::chrono::seconds(1));

}
</code></pre>
<p>这个测试用例使用API调用<code>GET /hello</code> 判断服务器是否相应预期的值</p>
<p>使用<code>ClientServerTestRunner</code> 运行测试服务器</p>
<h3 id="运行测试"><a class="header" href="#运行测试">运行测试</a></h3>
<p>在文件夹<code>test/</code> 创建文件<code>Tests.cpp</code></p>
<pre><code class="language-text-x-c-src">#include &quot;MyControllerTest.hpp&quot;

#include &lt;iostream&gt;

void runTests() {

  OATPP_RUN_TEST(MyControllerTest);
  
  // TODO - Add more tests here:
  // OATPP_RUN_TEST(MyAnotherTest);
  
}

int main() {

  oatpp::base::Environment::init();

  runTests();

  /* Print how much objects were created during app running, and what have left-probably leaked */
  /* Disable object counting for release builds using '-D OATPP_DISABLE_ENV_OBJECT_COUNTERS' flag for better performance */
  std::cout &lt;&lt; &quot;\nEnvironment:\n&quot;;
  std::cout &lt;&lt; &quot;objectsCount = &quot; &lt;&lt; oatpp::base::Environment::getObjectsCount() &lt;&lt; &quot;\n&quot;;
  std::cout &lt;&lt; &quot;objectsCreated = &quot; &lt;&lt; oatpp::base::Environment::getObjectsCreated() &lt;&lt; &quot;\n\n&quot;;

  OATPP_ASSERT(oatpp::base::Environment::getObjectsCount() == 0);

  oatpp::base::Environment::destroy();

  return 0;
}
</code></pre>
<p>在所有测试结束后，oatpp环境将会检查泄漏的对象</p>
<p>如果程序发生泄漏测试将会失败</p>
<blockquote>
<p>用于测试的可执行文件不应该在构建时使用<code>-DOATPP_DISABLE_ENV_OBJECT_COUNTERS</code> 这个连接标志</p>
</blockquote>
<h2 id="完整的工程代码"><a class="header" href="#完整的工程代码">完整的工程代码</a></h2>
<p><a href="https://github.com/oatpp/oatpp-starter">在这里</a></p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
            </nav>

        </div>

        <!-- Livereload script (if served using the cli tool) -->
        <script>
            const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
            const socket = new WebSocket(wsAddress);
            socket.onmessage = function (event) {
                if (event.data === "reload") {
                    socket.close();
                    location.reload();
                }
            };

            window.onbeforeunload = function() {
                socket.close();
            }
        </script>
        <script>
            window.playground_copyable = true;
        </script>
        <script src="elasticlunr.min.js"></script>
        <script src="mark.min.js"></script>
        <script src="searcher.js"></script>
        <script src="clipboard.min.js"></script>
        <script src="highlight.js"></script>
        <script src="book.js"></script>

        <!-- Custom JS scripts -->
        <script>
        window.addEventListener('load', function() {
            window.setTimeout(window.print, 100);
        });
        </script>
    </body>
</html>
